最後一天,如同昨天說的,今天至少會把IMA三個extension的test全部通過。
RV64M TEST
首先我們先試著透過原先建立好的環境試著跑看看RV64M
執行結果如下,目前看來是32bit的div和rem有問題。
根據log判斷是sign-extension有問題,檢查發現原先除法時會取後32bit,但資料型態還是int64,導致負數沒有正確被sign-extension,因此修改為以下程式碼。
reg[rd] = sext(((int32_t)(reg[rs1] & 0xffffffff)) / ((int32_t)(reg[rs2] & 0xffffffff)),32);
先取完32bit之後當成int32進行除法,再將除法結果做sign-extension。
修改之後遇到第二個問題,執行下去會報SEGFAULT floatpoint exception,判斷應該與特例有關,檢查發現在remw和divw時還是根據INT64_M去判斷MIN/-1的狀況,修改為INT32_M。
修改後,RV64M Pass。
RV64A TEST
執行RV64A Test,發現沒有遇到BUG,截至目前,我們已經完成一個支援RV64bit IMA的模擬器,並能夠通過RISC-V Test的RV64IMA。
Linux RUN
昨天文章有提到,要把Linux跑起來,首先要有load device tree binary的方法,再來要有一個假的UART來充當console。
我們先產出一個DTB,在先前安裝Spike時我們已經有Device tree compiler,因此我們只要自己寫一個DTS即可編譯出一個DTB。
範例的DTS如下,我們只留下我們需要的部分即可,像interrupt等目前沒有實現的部分可以先不要。 ref
/dts-v1/;
/ {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "riscv-minimal-nommu";
model = "riscv-minimal-nommu,qemu";
chosen {
bootargs = "earlycon=uart8250,mmio,0x10000000,1000000 console=hvc0";
};
memory@80000000 {
device_type = "memory";
reg = <0x00 0x80000000 0x00 0x3ffc000>;
};
cpus {
#address-cells = <0x01>;
#size-cells = <0x00>;
timebase-frequency = <0xf4240>;
cpu@0 {
phandle = <0x01>;
device_type = "cpu";
reg = <0x00>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64ima";
mmu-type = "riscv,none";
interrupt-controller {
#interrupt-cells = <0x01>;
interrupt-controller;
compatible = "riscv,cpu-intc";
phandle = <0x02>;
};
};
cpu-map {
cluster0 {
core0 {
cpu = <0x01>;
};
};
};
};
soc {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "simple-bus";
ranges;
uart@10000000 {
clock-frequency = <0x1000000>;
reg = <0x00 0x10000000 0x00 0x100>;
compatible = "ns16850";
};
poweroff {
value = <0x5555>;
offset = <0x00>;
regmap = <0x04>;
compatible = "syscon-poweroff";
};
reboot {
value = <0x7777>;
offset = <0x00>;
regmap = <0x04>;
compatible = "syscon-reboot";
};
};
};
將以上內容儲存為文件ALISS.dts,並輸入
dtc -I dts -O dtb -o ALISS.dtb ALISS.dts
同時我們實現DTB loader
bool loadDTB(const char* filename, uint64_t dtb_addr )
{
// Open the file
FILE* file = fopen(filename, "rb");
if (file == NULL) {
perror("Failed to open the file");
exit(EXIT_FAILURE);
}
// Get the file size
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
rewind(file);
// Allocate memory to store the file content
uint8_t* file_data = (uint8_t*)malloc(file_size);
if (file_data == NULL) {
perror("Memory allocation failed");
fclose(file);
exit(EXIT_FAILURE);
}
// Read the file content
size_t bytes_read = fread(file_data, 1, file_size, file);
if (bytes_read != file_size) {
perror("Failed to read the file");
free(file_data);
fclose(file);
exit(EXIT_FAILURE);
}
// Use memcpy to copy the file content into memory
memcpy(memory + dtb_addr, file_data, file_size);
// Close the file and free memory
fclose(file);
free(file_data);
return 0;
};
最後,為了讓Linux對外輸出可以顯示在我們的螢幕上,我們需要實作Uart的功能,透過在Load/Store指令的地方判斷是否有碰到Uart來實作,實作方法如下。
case 0x3: //LOAD
{
uint64_t rd = ((insn >> 7) & 0x1f);
uint64_t rs1 = ((insn >> 15) & 0x1f);
uint64_t imm = ((insn >> 20) & 0xfff);
if(reg[rs1] + sext(imm,12) == 0x10000005)
{
reg[rd] = 0x60;
break;
}
case 0x23: //STORE
{
uint64_t rs1 = ((insn >> 15) & 0x1f);
uint64_t rs2 = ((insn >> 20) & 0x1f);
uint64_t imm = ( ( insn & 0xfe000000 ) >> 20 ) | // [11:5]
( ( insn >> 7 ) & 0x1f ); //[4:0]
if(reg[rs1] + sext(imm,12) == 0x10000000)
{
printf("%c",(uint8_t)reg[rs2]);
break;
}
以上完成後,想像上應該就能夠跑Linux啦,GOGO!
Linux Run
有輸出內容代表Uart沒問題,但看起來是DTB的位置有問題,不過這個部分待我之後再來Debug,先簡單總結一下這三十天的心得。
最後的碎碎念,下次一定
感謝這三十天來所有路過的看官,以及追蹤這系列文章的朋友們。雖然最終沒有達到一開始所訂下的RV64IMA Linux的目標,但至少我們順利完成一個RV64IMA的模擬器,且能夠順利通過RISC-V官方的Test。中間真的幾度想要放棄,特別是連假的時候燒到38度在床上兩天的時候。幸好最後還是撐到完賽了。
最開始只是跟朋友聊天的時候一時興起,想要製作一個叫做ALISS的專案,最後不小心就參賽、進而催生出了這個模擬器。原本參賽前想說三十天而已,且是我平常有在接觸、較為熟悉的內容,難度應該不高。但最後卻發現要好好撐完這三十天真是困難,一直到差不多二十天的時候才開始漸漸習慣。
對我來說,鐵人賽最困難的地方比起把專案完成,怎麼樣完整地說一個故事才是更為困難的。我常常在要描述A的時候發現需要補充B,而在描述B的時候又需要描述CDE,怎麼樣碎片化知識但又要說的讓人聽得懂真的很難,非常佩服那些可以精準地用文字描述心裡想法的前輩們。而我也高估了自己下班之後的生產力XD,還記得剛開賽前兩天是假日所以有較多的時間及精力可以去寫文章,但一到上班日我就發現自己的腦細胞好像死光,下完班之後就虛脫不已,常常要睡一下之後才能好好地產出。
但同時,鐵人賽也讓我得到很多,我一方面學習了過去只有使用,但不知道怎麼架設的Jenkins, Gtest等環境,也學習怎麼用VSCode連接docker。同時也因為參加這次比賽,讓我比以往還要更認真的看RISC-V的Spec,我第一次那麼認真的研究每一道指令的每一個bit,也讓我手寫in-line assembly的能力有所上升XD。最後,我也慢慢在學習怎麼樣去分享知識給別人,在工作中需要解釋一些domain knowledge給其他部門的同事時也體感更為順利,這是我參賽前沒有預料到,但非常好的收穫。
下次還要不要參賽,我目前還沒有想法XD 我覺得不是分享已知的東西,而是藉這個機會開始學習一些不擅長的東西或許也不錯,例如怎麼製作一個遊戲等等的,但這些就讓明年的我來煩惱好了。謝謝收看,珍重再見。
Yoga